package io.github.jianjianghui.groot.spring.boot;

import cn.hutool.core.thread.ThreadUtil;
import io.github.jianjianghui.groot.core.Groot;
import io.github.jianjianghui.groot.core.GrootConfig;
import io.github.jianjianghui.groot.core.GrootRequest;
import io.github.jianjianghui.groot.core.GrootValidator;
import io.github.jianjianghui.groot.core.annotation.ContentFormatValidator;
import io.github.jianjianghui.groot.core.annotation.GrootMemoryUnit;
import io.github.jianjianghui.groot.spring.SpringGrootController;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 自动配置
 *
 * @author <a href="https://www.github.com/jianjianghui">Jianghui Jian<a/>
 * @date 2021/8/27 - 16:18
 */
@Import(SpringGrootController.class)
@EnableConfigurationProperties(GrootProperties.class)
@SpringBootConfiguration
public class GrootAutoConfiguration {

    @Bean
    public GrootValidator grootValidator(GrootProperties grootProperties, RedisConnectionFactory factory) {

        return new GrootValidator(grootMemoryUnit(grootProperties, factory));
    }

    @Bean
    public GrootMemoryUnit grootMemoryUnit(GrootProperties grootProperties, RedisConnectionFactory factory) {
        if (MemoryUnitEnum.REDIS == grootProperties.getMemoryUnit()) {
            return new GrootMemoryUnit() {
                final private RedisTemplate<String, GrootRequest> template = generateGrootRequestRedisTemplate(factory);
                final private ValueOperations<String, GrootRequest> valueOperations = template.opsForValue();
                final private String preKey = grootProperties.getRedisMemoryUnit().preKey;

                private RedisTemplate<String, GrootRequest> generateGrootRequestRedisTemplate(RedisConnectionFactory factory) {
                    RedisTemplate<String, GrootRequest> template = new RedisTemplate<>();
                    // 配置连接工厂
                    template.setConnectionFactory(factory);

                    template.setKeySerializer(RedisSerializer.string());
                    template.setValueSerializer(RedisSerializer.json());
                    template.setHashKeySerializer(RedisSerializer.string());
                    template.setHashValueSerializer(RedisSerializer.json());
                    template.afterPropertiesSet();
                    return template;
                }


                @Override
                public void add(Groot groot) {
                    valueOperations.set(preKey + groot.getGrootRequest().getIp().hashCode() + groot.getGrootRequest().getRequestId(), groot.getGrootRequest(), groot.getIdempotent().value(), TimeUnit.MILLISECONDS);
                }

                @Override
                public GrootRequest get(String ip, String requestId) {
                    return valueOperations.get(preKey + ip.hashCode() + requestId);
                }

                @Override
                public void delete(String ip, String requestId) {
                    template.delete(preKey + ip.hashCode() + requestId);
                }
            };
        }

        if (MemoryUnitEnum.LOCAL == grootProperties.getMemoryUnit()) {
            return new GrootMemoryUnit() {
                private final ScheduledThreadPoolExecutor scheduledExecutor = ThreadUtil.createScheduledExecutor(grootProperties.getLocalMemoryUnit().getAsyncCoreSize());
                private final Map<String, GrootRequest> grootRequestMap = new ConcurrentHashMap<>(8);

                @Override
                public void add(Groot groot) {
                    GrootRequest grootRequest = groot.getGrootRequest();
                    grootRequestMap.put(grootRequest.getIp() + grootRequest.getRequestId(), grootRequest);

                    scheduledExecutor.schedule(() -> grootRequestMap.remove(grootRequest.getIp() + grootRequest.getRequestId()), groot.getIdempotent().value(), TimeUnit.MILLISECONDS);

                }

                @Override
                public GrootRequest get(String ip, String requestId) {
                    return grootRequestMap.get(ip + requestId);
                }

                @Override
                public void delete(String ip, String requestId) {
                    grootRequestMap.remove(ip + requestId);
                }
            };
        }

        return null;
    }

    @Bean
    public GrootConfig grootConfig(GrootProperties grootProperties) {
        GrootConfig config = new GrootConfig();
        config.setEnable(grootProperties.isEnable());
        config.setHeaderTag(grootProperties.getHeaderTag());
        config.setHeaderTagNotFound(grootProperties.getHeaderTagNotFound());
        config.setHeaderTagIllegalContent(grootProperties.getHeaderTagIllegalContent());
        config.setHeaderTagVerificationFailed(grootProperties.getHeaderTagVerificationFailed());
        return config;
    }

    @Bean
    public ContentFormatValidator contentFormatValidator() {
        return s -> StringUtils.length(s) == 16;
    }

}
